package com.neuronbit.businessflow.flow.source;

import com.neuronbit.businessflow.flow.parser.DecisionNode;
import com.neuronbit.businessflow.flow.parser.Output;
import com.neuronbit.businessflow.flow.source.util.FieldBuilder;
import com.neuronbit.businessflow.flow.source.util.SourceContext;
import com.neuronbit.businessflow.intellij.model.Edge;
import com.neuronbit.businessflow.intellij.model.Node;
import com.neuronbit.businessflow.intellij.model.Param;
import com.neuronbit.businessflow.runtime.StepType;
import com.neuronbit.javaparser.ast.expr.MethodCallExpr;
import com.neuronbit.javaparser.ast.expr.NameExpr;
import com.neuronbit.javaparser.ast.stmt.BlockStmt;
import com.neuronbit.javaparser.ast.stmt.IfStmt;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.capitalize;

/**
 * The type Decision node source generator.
 */
@Getter
@Setter
public class DecisionStepJavaSource extends ConditionalStepJavaSource {

    /**
     * Instantiates a new Decision node source generator.
     *
     * @param node          the node
     * @param sourceContext the source context
     */
    public DecisionStepJavaSource(Node node, SourceContext sourceContext) {
        super(node, sourceContext);
    }

    @Override
    protected StepType getStepType() {
        return StepType.DECISION;
    }


    @Override
    public void writeStepCallStmt(BlockStmt methodBody) {
        DecisionNode decisionNode = (DecisionNode) getNode();
        Map<String, Param> inputs = decisionNode.getCallable().getParams();
        List<String> paramNames = new ArrayList<>();
        if (null != inputs) {
            paramNames = prepareStepParams(methodBody, inputs.values(), getFlowNodeSource());
        }
        Output output = decisionNode.getOutput();
        String re = "";
        String setStmt = null;
        if (output != null) {
            addImport(output.getType());
            re = output.getType().describe() + " " + output.getName() + "=";
            FieldBuilder fieldBuilder = FieldBuilder.builder(output.getName(), output.getType())
                    .withSetter()
                    .withGetter()
                    .prefix(getNode().getName());
            getContextSource().addField(fieldBuilder);
            setStmt = format("context.set%s(%s);", capitalize(getNode().getName()) + capitalize(output.getName()), output.getName());
        }
        //call the actual business processing logic
        if (null != decisionNode.getType() && StringUtils.isNoneBlank(re, decisionNode.getMethodName())) {
            addImport(decisionNode.getType());
            methodBody.addStatement(format("%s flow.getStepBeanProvider().getBean(%s.class).%s(%s);",
                    re,
                    decisionNode.getType().getName(),
                    decisionNode.getMethodName(),
                    String.join(",", paramNames)));
        }
        if (null != setStmt) {
            methodBody.addStatement(setStmt);
        }
    }

    @Override
    protected void afterStepCallStmt(BlockStmt stepCallStmt, BlockStmt methodBody) {
        super.afterStepCallStmt(stepCallStmt, methodBody);

        DecisionNode decisionNode = (DecisionNode) getNode();
        Output output = decisionNode.getOutput();
        if (null != output) {
            IfStmt ifStmt = new IfStmt();
            ifStmt.setCondition(new MethodCallExpr(format("context.get%s", capitalize(getNode().getName()) + capitalize(output.getName()))));
            BlockStmt thenStmt = new BlockStmt();
            ifStmt.setThenStmt(thenStmt);

            for (Edge outgoingEdge : getFlow().outgoingEdges(getNode())) {
                if (outgoingEdge.getValue().equalsIgnoreCase("true")) {
                    handlerSuccessor(outgoingEdge.getTargetNode(), thenStmt);
                }
            }
            BlockStmt elseStmt = new BlockStmt();
            ifStmt.setElseStmt(elseStmt);
            for (Edge outgoingEdge : getFlow().outgoingEdges(getNode())) {
                if (outgoingEdge.getValue().equalsIgnoreCase("false")) {
                    handlerSuccessor(outgoingEdge.getTargetNode(), elseStmt);
                }
            }

            methodBody.addStatement(ifStmt);
        }
    }
}
